package com.lj.back.manage.scaffold.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.lj.back.manage.scaffold.common.annotation.redis.MyCacheEvict;
import com.lj.back.manage.scaffold.common.annotation.redis.MyCacheable;
import com.lj.back.manage.scaffold.common.constant.RedisConstant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Parameter;
import java.time.Duration;
import java.util.Arrays;

/**
 * Redis配置类
 * @author lv
 * @version 1.0.0
 */
@Configuration
@ConditionalOnClass({RedisAutoConfiguration.class})
public class RedisConfig extends CachingConfigurerSupport {

    private static ExpressionParser parser = new SpelExpressionParser();

    /**
     * 1、在{@link com.lj.back.manage.scaffold.common.constant.RedisConstant#KEY_PREFIX}配置项目统一前缀
     * 2、在{@link com.lj.back.manage.scaffold.common.constant.RedisConstant#EXPIRE_DEFAULT}配置默认失效时间
     * 3、默认注解key组成规则，没有手动指定key的情况下为:统一前缀+类名+方法名+方法参数，手动指定key的情况下为:统一前缀+cacheNames+key
     * 4、自定义注解MyCacheable和MyCacheEvict，可以通过newKey指定key，组成规则为：统一前缀+类名+方法名+newKey
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (object,method,objects)->{
            //获得注解
            MyCacheable myCacheable = AnnotationUtils.findAnnotation(method, MyCacheable.class);
            MyCacheEvict myCacheEvict = AnnotationUtils.findAnnotation(method, MyCacheEvict.class);
            //格式化缓存key字符串
            StringBuilder sb = new StringBuilder();
            //追加类名
            sb.append(object.getClass().getName());
            sb.append("_");
            //追加方法名
            sb.append(method.getName());
            sb.append("_");
            if (myCacheable != null){
                //获得注解的newKey值
                String newKey = myCacheable.newKey();
                //获取方法的参数集合
                Parameter[] parameters = method.getParameters();
                StandardEvaluationContext context = new StandardEvaluationContext();
                //遍历参数，以参数名和参数对应的值为组合，放入StandardEvaluationContext中
                for (int i = 0; i< parameters.length; i++) {
                    context.setVariable(parameters[i].getName(), objects[i]);
                }
                //根据newKey来解析获得对应值
                Expression expression = parser.parseExpression(newKey);
                String value = expression.getValue(context, String.class);
                sb.append(value);
            }else {
                Arrays.stream(objects).forEach(o -> sb.append(o).append("_"));
            }
            return sb.toString();
        };
    }



    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(factory);
        //关闭事务
        redisTemplate.setEnableTransactionSupport(false);
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        // 指定要序列化的域，field,get和set,以及修饰符范围，ANY是都有包括private和public
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //过滤掉value为null的值
        mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        //反序列化LocalDateTime
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.registerModule(new JavaTimeModule());
        serializer.setObjectMapper(mapper);
        MyRedisKeySerializer stringRedisSerializer = new MyRedisKeySerializer();
        redisTemplate.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(serializer);
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate){
        //redis基本配置
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
                //设置前缀
                .computePrefixWith((name)-> RedisConstant.KEY_PREFIX +name+"_")
                //设置过期时间
                .entryTtl(Duration.ofSeconds(RedisConstant.EXPIRE_DEFAULT));
        //构造一个redis缓存管理器
        RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder
                // redis 连接工厂
                .fromConnectionFactory(redisTemplate.getConnectionFactory())
                //设置缓存配置
                .cacheDefaults(redisCacheConfiguration)
                .transactionAware()
                .build();
        return redisCacheManager;
    }


    /**
     * 对hash类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate redisTemplate) {
        return redisTemplate.opsForZSet();
    }
}
